/*******************************************************************************
 * Copyright (c) 2011, 2012 Link Management & Technology S.p.A. 
 * via R. Scotellaro, 55 - 73100 - Lecce - http://www.linksmt.it
 * All rights reserved. 
 *
 * Contributors:
 *     Links Management & Technology S.p.A. - initial API and implementation
 *******************************************************************************/
package it.linksmt.tooling.jpaddlplugin.impl;

import it.linksmt.tooling.jpaddlplugin.DDLParameters;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.apache.maven.plugin.logging.Log;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.logging.SessionLog;

/**
 * Utility per la generazione dello schema di DB senza necessità di drivers o altro. <br/>
 * Alla fine dell'elaborazione, il risultato si troverà nella directory <strong>target/</strong>.
 * <br/><br/>
 * <i>Code adapted from https://bugs.eclipse.org/bugs/show_bug.cgi?id=297544</i>
 * 
 * @author Mario Scalas
 */
public class DDLGeneratorTool {
	
	private DDLParameters parameters;

	private DDLClassLoader ddlClassLoader;

	private Log log;
	
	/**
	 * Construct a new generator instance.
	 * 
	 * @param parameters generation parameters 
	 * @param classLoader 
	 * @param logger Maven logger instance
	 */
	public DDLGeneratorTool( DDLParameters parameters, DDLClassLoader classLoader, Log logger ) {
		this.log = logger;
		this.parameters = parameters;
		this.ddlClassLoader = classLoader;
	}

	public void generateSchema() throws Exception {
		log.info( 
				String.format( "Generating SQL schema for persistence unit %s (%s) ...", parameters.getPersistenceUnitName(), parameters.getTargetDbPlatform() ) );

//		final String persistenceUnitName = "osapulie";
//		final String persistenceUnitFile = "META-INF/ddl-persistence.xml";
		String ddlOutputDir = parameters.getDdlOutputDir();
		String ddlCreateFileName = parameters.getDdlCreateFileName();
		String ddlDropFileName = parameters.getDdlDropFileName();
//		String ddlGenerationMode = PersistenceUnitProperties.DROP_AND_CREATE;
//		String ddlGenerationLoggingLevel = SessionLog.FINEST_LABEL;
//		String targetDbPlatform = MySQLPlatform.class.getName();
//		String targetDbPlatform = H2Platform.class.getName();
		// Note: Eclipselink will put the generated schema files as tmp files and we are going to rename them to the final ones later on. 
		File tmpCreateFileName = File.createTempFile( "ddl_create_", ".tmp" );
		File tmpDropFileName = File.createTempFile( "ddl_drop_", ".tmp" );
		
		Map<String, Object> properties = new HashMap<String, Object>();

		properties.put( PersistenceUnitProperties.LOGGING_LEVEL, parameters.getDdlGenerationLoggingLevel() );
		properties.put( PersistenceUnitProperties.CATEGORY_LOGGING_LEVEL_ + SessionLog.EJB_OR_METADATA, SessionLog.WARNING_LABEL );
		
		properties.put( PersistenceUnitProperties.JDBC_DRIVER, CreateSchemaMockDriver.class.getName() );
		properties.put( PersistenceUnitProperties.JDBC_URL, "emulate:dummy" );
		properties.put( PersistenceUnitProperties.CONNECTION_POOL_MIN, "0" );

		properties.put( PersistenceUnitProperties.DDL_GENERATION, parameters.getDdlGenerationMode() );
		properties.put( PersistenceUnitProperties.DDL_GENERATION_MODE, PersistenceUnitProperties.DDL_SQL_SCRIPT_GENERATION );
		properties.put( PersistenceUnitProperties.CREATE_JDBC_DDL_FILE, tmpCreateFileName.getName() );
		properties.put( PersistenceUnitProperties.DROP_JDBC_DDL_FILE, tmpDropFileName.getName() );
		properties.put( PersistenceUnitProperties.APP_LOCATION, tmpCreateFileName.getParent() );
		
		properties.put( PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML, parameters.getPersistenceUnitFile() );
		properties.put( PersistenceUnitProperties.TARGET_DATABASE, parameters.getTargetDbPlatform() );

		// Ensure eclipselink uses the maven plugin class loader
		properties.put( PersistenceUnitProperties.CLASSLOADER, ddlClassLoader );

		ensureOutputDirectoryExists();
		
		ClassLoader tccl = Thread.currentThread().getContextClassLoader();
		try {
			Thread.currentThread().setContextClassLoader( ddlClassLoader );

			EntityManagerFactory emf = Persistence.createEntityManagerFactory( parameters.getPersistenceUnitName(), properties );
			EntityManager em = emf.createEntityManager();
			em.close();
			emf.close();

			// These are required because EclipseLink's SQL scripts do not have trailing ";" on each line
			fixSqlLineTerminators( tmpCreateFileName, new File( ddlOutputDir + File.separator + ddlCreateFileName ) );
			fixSqlLineTerminators( tmpDropFileName, new File( ddlOutputDir + File.separator + ddlDropFileName ) );
		} finally {
			Thread.currentThread().setContextClassLoader( tccl );
		}
		
		log.info( "SQL schema generation tenerminated succesfully." );
	}

	/**
	 * Create output directory if it does not exist.
	 */
	private void ensureOutputDirectoryExists() {
		File outputDir = new File( parameters.getDdlOutputDir() );
		if (!outputDir.exists()) {
			outputDir.mkdirs();
		}
	}

	/**
	 * @param file
	 */
	private void fixSqlLineTerminators( File inputFile, File outFile ) {
		log.debug( String.format( "Fixing %s ...", inputFile.getAbsolutePath() ) );
		
		BufferedReader br = null;
		BufferedWriter bw = null;
		try {
			br = new BufferedReader( new FileReader( inputFile ) );
			
			log.debug( String.format( "Creating work file %s ...", outFile ) );
			bw = new BufferedWriter( new FileWriter( outFile ) );
			
			List<String> lines = new ArrayList<String>();
			
			String line = null;
			while ((line = br.readLine()) != null) {
				if (!line.endsWith( ";" )) {
					lines.add( line + ";" );
				}
				log.debug( line );
			}

			int n = lines.size() - 2; // The last two lines are duplicates of the former 2 :S
			for (int i = 0; i < n; i++) {
				String l = lines.get( i );
				bw.write( l );
				bw.append( ";\n" ); // Add line eding ";"
			}

			inputFile.delete();
			log.debug( String.format( "Done fixing %s!", outFile.getAbsolutePath() ) );
		} catch (Exception e) {
			log.error( e.getMessage(), e );
		} finally {
			closeStream( br );
			closeStream( bw );
		}
	}

	private void closeStream( Closeable stream ) {
		if (stream != null) {
			try {
				if (stream instanceof BufferedWriter) {
					BufferedWriter bw = (BufferedWriter) stream;
					bw.flush();
				}
				stream.close();
			} catch (IOException e) { 
				log.error( e );
			}
		}
	}
}
